iT邦幫忙

2025 iThome 鐵人賽

DAY 6
0
自我挑戰組

《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》系列 第 6

Day6 - 持續成長學習藍圖 - JavaScript (非同步程式_Promise.all、Promise.race、錯誤處理)

  • 分享至 

  • xImage
  •  

昨天先把非同步的基本觀念(callback → promise → async/await)走過一遍。
今天升級到「同時做很多事」以及「錯誤處理」:Promise.all、Promise.race,還會自己做一個 延遲工具 來模擬 API 速度與錯誤。


先做兩個小工具:delay 與 mockFetch

為了練習方便,我先做兩個小工具函式:

// 延遲工具:回傳一個在 ms 毫秒後 resolve 的 Promise
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

// 模擬 API:過 ms 毫秒後成功或失敗
function mockFetch(name, ms, shouldFail = false) {
  return new Promise(async (resolve, reject) => {
    await delay(ms);
    if (shouldFail) {
      reject(new Error(`${name} 失敗(延遲 ${ms}ms)`));
    } else {
      resolve({ name, ms, data: `這是 ${name} 的資料` });
    }
  });
}

Promise.all:等「全部」都回來

情境:同時打三個 API,要全部都成功才算成功;其中一個失敗就整組失敗。

async function runAll() {
  try {
    console.time("ALL");
    const result = await Promise.all([
      mockFetch("users", 800),
      mockFetch("posts", 1200),
      mockFetch("comments", 600),
    ]);
    console.timeEnd("ALL");

    // result 是依照陣列順序回來(不是依完成時間)
    console.log("ALL 成功,結果:", result);
  } catch (err) {
    console.error("ALL 發生錯誤:", err.message);
  }
}

runAll();

重點觀察:

  • 三個請求同時出發。
  • Promise.all 只有在三個都成功才會 resolve。
  • 其中任何一個失敗就會直接進 catch

Promise.race:誰最快就用誰

情境:我只想要最快回來的結果(例如多個鏡像站、或超時改用備援)。

async function runRace() {
  try {
    const fastest = await Promise.race([
      mockFetch("A 伺服器", 900),
      mockFetch("B 伺服器", 500),
      mockFetch("C 伺服器", 700),
    ]);
    console.log("RACE 最快回來:", fastest);
  } catch (err) {
    console.error("RACE 發生錯誤:", err.message);
  }
}

runRace();

重點觀察:

  • 只要有第一個 promise 變成 fulfilled 或 rejected,race 就會結束。
  • 真的有可能第一個回來的是「失敗」,這時會直接進 catch

async function 的錯誤處理(try/catch)

把錯誤處理放在 async function 裡,用 try/catch 包起來就好讀很多:

async function fetchAllWithTryCatch() {
  try {
    const [users, posts, comments] = await Promise.all([
      mockFetch("users", 300),
      mockFetch("posts", 600),
      mockFetch("comments", 900, /* shouldFail */ false),
    ]);
    console.log("users:", users.data);
    console.log("posts:", posts.data);
    console.log("comments:", comments.data);
  } catch (error) {
    console.error("抓資料失敗:", error.message);
  }
}

fetchAllWithTryCatch();

加入錯誤模擬:其中一個 API 失敗

需求:同時抓三個 API,但「其中一個」失敗了,我想要:

  • try/catch 抓到錯誤訊息。
  • 或者改用 Promise.allSettled,取得每個請求的結果(成功或失敗),自己決定要怎麼處理。

用 Promise.all(會直接失敗)

async function allButOneFails() {
  try {
    const data = await Promise.all([
      mockFetch("users", 500),
      mockFetch("posts", 700, /* shouldFail */ true), // 故意失敗
      mockFetch("comments", 400),
    ]);
    console.log("這行不會被執行(因為上面會丟錯)", data);
  } catch (e) {
    console.error("ALL:其中一個失敗 → 直接進 catch:", e.message);
  }
}

allButOneFails();

用 Promise.allSettled(拿到每個結果的狀態)

雖然今天主題不是它,但在「有失敗也想看成功的結果」時非常好用。

async function allSettledExample() {
  const results = await Promise.allSettled([
    mockFetch("users", 500),
    mockFetch("posts", 700, true), // 失敗
    mockFetch("comments", 400),
  ]);

  const successes = results
    .filter(r => r.status === "fulfilled")
    .map(r => r.value);

  const failures = results
    .filter(r => r.status === "rejected")
    .map(r => r.reason.message);

  console.log("成功清單:", successes);
  console.log("失敗清單:", failures);
}

allSettledExample();

題目實作:同時抓三個 API,等全部完成後輸出

我用前面的 mockFetch 來模擬三個 API:使用者、文章、留言。
第一段:全部成功 → 輸出整合結果。
第二段:其中一個失敗 → 用 try/catch 抓錯,或改用 allSettled 部分採納。

✅ 版本 A:全成功(Promise.all)

// ✅ 版本 A:全成功(Promise.all)
async function demoAllSuccess() {
  try {
    const [users, posts, comments] = await Promise.all([
      mockFetch("users", 800),
      mockFetch("posts", 1200),
      mockFetch("comments", 600),
    ]);

    console.log("成功整合:", {
      users: users.data,
      posts: posts.data,
      comments: comments.data,
    });
  } catch (e) {
    console.error("不會觸發(此版都成功)", e.message);
  }
}

⚠️ 版本 B:其中一個失敗(Promise.all + try/catch)

// ⚠️ 版本 B:其中一個失敗(Promise.all + try/catch)
async function demoAllWithFailure() {
  try {
    const [users, posts, comments] = await Promise.all([
      mockFetch("users", 800),
      mockFetch("posts", 1200, true), // 故意失敗
      mockFetch("comments", 600),
    ]);
    console.log("這行不會到達:", users, posts, comments);
  } catch (e) {
    console.error("有一個失敗 → 直接進 catch:", e.message);
  }
}

✅ 版本 C:其中一個失敗,但我想看「成功的那些」(allSettled)

// ✅ 版本 C:其中一個失敗,但我想看「成功的那些」(allSettled)
async function demoAllSettled() {
  const results = await Promise.allSettled([
    mockFetch("users", 800),
    mockFetch("posts", 1200, true), // 失敗
    mockFetch("comments", 600),
  ]);

  const ok = results.filter(r => r.status === "fulfilled").map(r => r.value);
  const ng = results.filter(r => r.status === "rejected").map(r => r.reason.message);

  console.log("完成但成功的結果:", ok);
  console.log("失敗原因:", ng);
}

run

// 跑看看
demoAllSuccess().then(() => demoAllWithFailure()).then(() => demoAllSettled());

Promise.race 的實戰想像:超時備援

一個常見的用法是「設定超時」。如果 API 太久沒回來,就用備援或直接放棄。

// 超時工具:若超過 ms 就 reject
function timeout(ms) {
  return new Promise((_, reject) =>
    setTimeout(() => reject(new Error(`超過 ${ms}ms,逾時`)), ms)
  );
}

async function withTimeout() {
  try {
    // 真的 API(這裡用 mock)
    const realRequest = mockFetch("慢吞吞 API", 2000);

    // 跟 timeout 競速,誰先回來就採用誰
    const res = await Promise.race([realRequest, timeout(1000)]);
    console.log("API 回來了:", res);
  } catch (e) {
    console.error("RACE 逾時或失敗:", e.message);
  }
}

withTimeout();

🎯 學習心得 / 今日收穫

  • Promise.all:適合「要嘛全有、要嘛全無」的情境;其中一個失敗就整組失敗。

  • Promise.race:適合「要最先回來的結果」,包含最快失敗也會觸發。

  • try/catchasync 函式裡很好寫,讓錯誤處理清楚很多。

  • allSettled 雖然不在今天的清單,但在「想要看全部結果(成功/失敗)」時很好用。

自己做 delay()mockFetch() 來練習,真的能更理解非同步的節奏。

今天最有感的是:同時發出多個請求、再決定要等誰或怎麼處理錯誤,這件事其實不難,關鍵是選對工具。
明天我會把這些東西帶到「模組化與 NPM」,把程式分檔、用套件,讓小專案更像樣。💪


上一篇
Day5 - 持續成長學習藍圖 - JavaScript (非同步程式_Promise、async/await)
下一篇
Day7 - 持續成長學習藍圖 - JavaScript (模組化與 NPM)
系列文
《轉職學習日記:JavaScript × Node.js × TypeScript × Docker × AWS ECS》8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言